package common.peer;

import java.math.BigInteger;
import java.util.Collection;

import se.sics.asdistances.ASDistances;
import se.sics.kompics.address.Address;
import se.sics.kompics.p2p.overlay.OverlayAddress;

public final class PeerAddress extends OverlayAddress implements Comparable<PeerAddress> {
	private static final long serialVersionUID = -7582889514221620065L;
	private static final int ID_SPACE = (int)Math.pow(2,13);
	private static final int MAX_AS_NBR = 65536;
	private final BigInteger peerId;

	//-------------------------------------------------------------------
	public PeerAddress(Address address, BigInteger peerId) {
		super(address);
		this.peerId = peerId;
	}

	//-------------------------------------------------------------------
	public BigInteger getPeerId() {
		return peerId;
	}

	//-------------------------------------------------------------------
	public int compareTo(PeerAddress that) {
		return peerId.compareTo(that.peerId);
	}

	//-------------------------------------------------------------------
	@Override
	public String toString() {
		return peerId.toString();
	}

	//-------------------------------------------------------------------
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result
		+ ((peerId == null) ? 0 : peerId.hashCode());
		return result;
	}

	//-------------------------------------------------------------------
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		PeerAddress other = (PeerAddress) obj;
		if (peerId == null) {
			if (other.peerId != null)
				return false;
		} else if (!peerId.equals(other.peerId))
			return false;
		return true;
	}

	/**
	 * 
	 * @param a
	 * @param b
	 * @return true if a is closer to this instance than b
	 */
	public boolean closerThanOnARing(PeerAddress a, PeerAddress b) {
		return (distanceOnARing(this, a).compareTo(distanceOnARing(this, b)) < 0);
	}

	/**
	 * Returns the distance between a and b
	 * @param a
	 * @param b
	 * @return
	 */
	public static BigInteger distanceOnARing(PeerAddress a, PeerAddress b) {
		if (a.getPeerId().compareTo(b.getPeerId()) <= 0) {
			return b.getPeerId().subtract(a.getPeerId());
		} else {
			return BigInteger.valueOf(ID_SPACE).subtract(a.getPeerId()).add(b.getPeerId());
		}
	}

	public static int distanceOnARing(int a, int b){
		return Math.min(Math.abs(b-a), ID_SPACE - Math.abs(b-a));
	}

	public static PeerAddress closestOnARing(PeerAddress a, PeerAddress b, PeerAddress target){
		if(distanceOnARing(target, a).intValue() < distanceOnARing(target, b).intValue()){
			return a;
		}
		else {
			return b;
		}
	}

	public static PeerAddress closestOnARing(Collection<PeerAddress> peers,BigInteger target) {
		int minDistance = Integer.MAX_VALUE;
		PeerAddress currentPeer = null;

		for(PeerAddress peer : peers){
			int distance = distanceOnARing(peer.getPeerId().intValue(), target.intValue());
			if(distance == minDistance){
				if(peer.getPeerId().compareTo(currentPeer.getPeerId()) < 0){
					currentPeer = peer;
				}
			}
			if(distance < minDistance){
				currentPeer = peer;
				minDistance = distance;
			}
		}
		return currentPeer;
	}

	public int getRankFinger(final int i, PeerAddress toRank) {
		if(toRank.equals(this)) {
			return MAX_AS_NBR*2;
		}
		ASDistances d = ASDistances.getInstance();
		if((toRank.getPeerId().intValue() >= (this.getPeerId().intValue()+(int)Math.pow(2, i))%ID_SPACE) && 
				(toRank.getPeerId().intValue() < (this.getPeerId().intValue()+(int)Math.pow(2, i+1))%ID_SPACE)) {
			return (int) d.getDistance(this.getPeerAddress().getIp().getHostAddress(), toRank.getPeerAddress().getIp().getHostAddress());
		} else {
			return MAX_AS_NBR + d.getDistance(this.getPeerAddress().getIp().getHostAddress(), toRank.getPeerAddress().getIp().getHostAddress());
		}
	}
}
